
#include "Service.h"
#include <experimental/filesystem>
#include "IPackageResolver.h"
#include "myLog.h"
#include "Misc.h"
#include "Conf.h"
#include "Proc.h"

#include "Wings.h"



using namespace std;


Service::~Service() 
{
    this->deinit();
}

Service *Service::instance() 
{
    static Service singleton;

    return &singleton;
}

static bool isLogConfFileValid(const string &path) 
{
    if (true == path.empty()) 
    {
        fprintf(stderr, "invalid log configuration file, detail: file path is nil.\n");
        return false;
    }

    if (false == experimental::filesystem::exists(path)) 
    {
        fprintf(stderr, "invalid log configuration file, detail: file '%s' is not exists.\n", path.c_str());
        return false;
    }

    if (false == experimental::filesystem::is_regular_file(path)) 
    {
        fprintf(stderr, "invalid log configuration file, detail: file '%s' is not a regular file.\n", path.c_str());
        return false;
    }

    return true;
}

static Result wingsInit() 
{
    Wings::wingInit();
    qlog_i("wings version, %d.%d.%d", Wings::majorVer(), Wings::minorVer(), Wings::patchVer());
    auto conf = Conf::currentConf();
    auto ret = Wings::setMaxSize(conf->ramfs.maxSize);
    if (0 != ret) 
    {
        auto err = Result::create(ErrorKind::ExternalLibError, "wings init failed, set max size");
        err.appendInfo(Wings::errorInfo(ret));

        return err;
    }

    ret = Wings::setMountPoint(conf->ramfs.mountPoint.c_str());
    if (0 != ret) 
    {
        auto err = Result::create(ErrorKind::ExternalLibError, "wings init failed, set mount point");
        err.appendInfo(Wings::errorInfo(ret));

        return err;
    }

    Wings::setFilePath(conf->ramfs.mountPoint.c_str());
    Wings::setConfiguration(conf->ramfs.description.c_str());

    qlog_d("wings max size: %d bytes, mount point: '%s'", Wings::maxSize(), Wings::mountPoint());

    return Result();
}

Result Service::init() 
{
    auto exePath = executionPath();
    if (true == exePath.empty()) 
    {
        auto err = Result::create(ErrorKind::OsRelatedError);
        err.appendInfo("can not locate execution path");
        return err;
    }
    string logConfFileVec[] = {
        exePath + "mylog.conf",
        exePath + "conf/mylog.conf",
    };

    bool ret = false;
    for (const auto& item: logConfFileVec) 
    {
        ret |= isLogConfFileValid(item);
        if (true == ret) 
        {
            ret = my_logInit(item.c_str());
            if(0 != ret) 
            {
                fprintf(stderr, "init log with configuration file '%s' failed.\n", item.c_str());
            }
            else 
            {
                qlog_i("logger init done('%s').", item.c_str());
                break;
            }
        }
    }
    if (0 != ret) 
    {
        fprintf(stderr, "logger init failed.\n");
        auto err = Result::create(ErrorKind::InitFailed, "logger init failed.");

        return err;
    }


    ret = false;
    Result res;
    string ConfFileVec[] = {
        exePath + "Conf.ini",
        exePath + "conf/Conf.ini",
    };

    for (const auto& item: ConfFileVec) 
    {
        res = Conf::loadConf(item);
        ret |= res.isOk();
        if (true == ret) 
        {
            qlog_i("parse application conf from '%s' done.", item.c_str());
            break;
        }
        else 
        {
            qlog_e("parse application conf failed, detail: %s", res.toString().c_str());
        }
    }
    if (false == ret) 
    {
        auto info = "finally, load application conf failed.";
        auto err = Result::create(ErrorKind::ExecuteFailed);
        err.appendInfo(info);

        return err;
    }

    qlog_i("wings wrapper version: %d.%d.%d", majorVersion(), minorVersion(), patchVersion());

    res = wingsInit();
    if (false == res.isOk()) 
    {
        return res;
    }
    qlog_d("wings init done.");
    
    res = IPackageResolver::instance()->init();
    if (false == res.isOk()) 
    {
        return res;
    }
    qlog_d("package resolver(%s) init done.", IPackageResolver::instance(PACKAGE_RESOLVER_INDEX_UDP)->type().c_str());


    // res = IPackageResolver::instance(PACKAGE_RESOLVER_INDEX_TCP)->init();
    // if (false == res.isOk()) 
    // {
    //     return res;
    // }
    // qlog_d("package resolver(%s) init done.", IPackageResolver::instance(PACKAGE_RESOLVER_INDEX_TCP)->type().c_str());

    return Result();
}

#define DEBUG 1

void Service::update(IPackageResolver *resolver) 
{
    string pkgName;
    vector<uint8_t> pkg;
    int ret = -1;
    auto res = resolver->resolve(pkgName, pkg);
    if (false == res.isOk()) 
    {
        if (ErrorKind::InvalidMessageReceiver == res.m_kind) 
        {
            qlog_d("resolver(%s) execute failed, detail: %s", resolver->type().c_str(), res.m_message.c_str());
        }
        else 
        {
            qlog_e("resolver(%s) execute failed, detail: %s", resolver->type().c_str(), res.m_message.c_str());
        }
        
        return;
    }

    qlog_v("resolver '%s' resolve package '%s', package size is %d bytes", resolver->type().c_str(), pkgName.c_str(), pkg.size());

    m_flagLock.lock();
    if (true == m_updateFlag) 
    {
        m_flagLock.unlock();
        qlog_e("resolver(%s) execute failed, detail: another resolver is updating.", resolver->type().c_str());
        resolver->respond(false, "another resolver is updating.");
        return;
    }
    m_updateFlag = true;
    m_flagLock.unlock();

auto retConf = Conf::currentConf();
//远程更新方式
if(retConf->debug) 
{
    pkgName = "./" + pkgName;
    FILE* fd = fopen(pkgName.c_str(),"w+");
    if(fd == NULL){
        qlog_e("fopen failed %s",pkgName.c_str());
    }
    if(fwrite(pkg.data() ,pkg.size() ,1 ,fd) == 1){
        qlog_i("write %s finished",pkgName.c_str());
        fseek(fd,0,SEEK_SET);      //将文件读写指针偏移到文件头
        fclose(fd);    //关闭文件
        if( !retConf->shell.empty() )
        {
            std::string m_cmd = string("Shell task Scheduler:") + retConf->shell;
            resolver->respond(true,m_cmd);
            qlog_i("Shell task Scheduler:%s",retConf->shell.c_str());
            m_cmd = string("sudo ./") +  retConf->shell;
            std::string resShell = shell(m_cmd);
            qlog_w("%s",resShell.c_str());
            resolver->respond(true,resShell);
            qlog_i("wings update done.");
        }
        
    }else{
        qlog_e("write %s failed",pkgName.c_str());
        fclose(fd);    //关闭文件
        resolver->respond(true,pkgName + string("write is failed."));
    }
} 
else 
{
    if (false == Wings::mounted()) 
    {
        ret = Wings::mount();
        if (0 != ret) 
        {
            qlog_e("wings mount failed, detail: %s", Wings::errorInfo(ret));
            resolver->respond(false, string("mount failed, ") + Wings::errorInfo(ret));
            goto exit0;
        }
    }

    ret = Wings::decompressBinary(pkg.data(), pkg.size(), pkgName.c_str());
    if (0 != ret) 
    {
        qlog_e("wings decompress failed, detail: %s", Wings::errorInfo(ret));
        resolver->respond(false, string("decompress failed, ") + Wings::errorInfo(ret));
        goto exit1;
    }

    ret = Wings::updatePackage();
    if (0 != ret) 
    {
        qlog_e("wings update failed, detail: %s", Wings::errorInfo(ret));
        resolver->respond(false, string("update failed, ") + Wings::errorInfo(ret));
        goto exit1;
    }

    resolver->respond(true);
    qlog_i("wings update done.");

exit1:
    ret = Wings::umount();
    if (0 != ret) 
    {
        qlog_e("wings umount failed, detail: %s", Wings::errorInfo(ret));
    }
}
exit0:
    m_flagLock.lock();
    m_updateFlag = false;
    m_flagLock.unlock();
    
}

void Service::deinit() 
{
    if (true == m_udpResolverWorker.joinable()) 
    {
        m_udpResolverWorker.join();
    }
    else 
    {
        m_udpResolverWorker.detach();
    }
    if (false == m_tcpResolverExitFlag) 
    {
        m_tcpResolverExitFlag = true;
        if (true == m_tcpResolverWorker.joinable()) 
        {
            m_tcpResolverWorker.join();
        }
        else 
        {
            m_tcpResolverWorker.detach();
        }
    }
}

bool Service::run() 
{
    Result res = this->init();
    if (false == res.isOk()) 
    {
        fprintf(stderr,"service init failed, detail: %s\n", res.m_message.c_str());
        qlog_e("service init failed, detail: %s", res.m_message.c_str());

        return false;
    }
    
    // create tcp/udp resolver thread.
    setThreadName("udp worker");

    m_tcpResolverExitFlag = false;
    m_tcpResolverWorker = thread([&]{
        while(true) 
        {
            if (true == this->m_tcpResolverExitFlag) 
            {
                break;
            }

            this->update(IPackageResolver::instance(PACKAGE_RESOLVER_INDEX_TCP).get());

        }
    });
    setThreadName("tcp worker", m_tcpResolverWorker.native_handle());

    while(true) 
    {
        this->update(IPackageResolver::instance().get());
    }

    return true;
}
